package com.itaming.lycheeframework.logging.aspect;

import cn.hutool.core.text.AntPathMatcher;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.itaming.lycheeframework.logging.properties.LycheeLoggingProperties;
import com.itaming.lycheeframework.support.utils.ArrayUtil;
import com.itaming.lycheeframework.support.utils.CollectionUtil;
import com.itaming.lycheeframework.support.utils.StringPool;
import com.itaming.lycheeframework.support.utils.WebUtil;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;

import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

/**
 * 请求日志切面类
 *
 * @author A.Ming
 */
@Aspect
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
@Slf4j
@RequiredArgsConstructor
public class RequestLogAspect {

    /**
     * 请求成功日志模板字符串
     */
    private static final String REQUEST_LOG_SUCCESS = """
        \n==================== API Request Info ====================
        🚀 >>> {} {}
        ⌛ Time ......... : {}ms
        🌍 ClientIP ..... : {}
        📝 Headers ...... : {}
        🎯 Handler ...... : {}
        📦 Parameters ... : {}
        ✅ Result ....... : {}
        ==========================================================
        """;

    /**
     * 请求失败日志模板字符串
     */
    private static final String REQUEST_LOG_FAILURE = """
        \n==================== API Request Info ====================
        🚀 >>> {} {}
        ⌛ Time ......... : {}ms
        🌍 ClientIP ..... : {}
        📝 Headers ...... : {}
        🎯 Handler ...... : {}
        📦 Parameters ... : {}
        ❌ Exception .... : {}
        ==========================================================
        """;

    /**
     * 路径匹配器
     */
    private final AntPathMatcher antPathMatcher = new AntPathMatcher();

    /**
     * ObjectMapper
     */
    private final ObjectMapper objectMapper;

    /**
     * 请求日志配置参数
     */
    private final LycheeLoggingProperties.RequestLog requestLogProperties;

    /**
     * 请求日志切面方法
     *
     * @param joinPoint 切点
     * @return 原始方法返回值
     * @throws Throwable
     */
    @Around("@within(org.springframework.web.bind.annotation.RestController) " +
        "|| @within(org.springframework.web.bind.annotation.ResponseBody) " +
        "|| @annotation(org.springframework.web.bind.annotation.ResponseBody)")
    public Object requestLog(ProceedingJoinPoint joinPoint) throws Throwable {
        HttpServletRequest request = WebUtil.getRequest();
        if (request == null) {
            return joinPoint.proceed();
        }

        // 排除
        String requestUri = request.getRequestURI();
        if (shouldSkip(requestUri)) {
            return joinPoint.proceed();
        }

        // 起始时间
        long start = System.currentTimeMillis();

        // 请求日志所需参数
        String requestUrl = request.getRequestURL().toString();
        String requestMethod = request.getMethod();
        String clientIP = WebUtil.getClientIP(request);
        String className = joinPoint.getTarget().getClass().getSimpleName();
        String methodName = ((MethodSignature) joinPoint.getSignature()).getMethod().getName();
        String handlerName = className + StringPool.DOT + methodName;
        String headersJson = serializeHeaders(request);
        String parameters = serializeParameters(joinPoint.getArgs());

        try {
            // 执行方法
            Object result = joinPoint.proceed();

            // 响应日志
            String responseString = serializeObject(result);
            long time = System.currentTimeMillis() - start;
            log.info(REQUEST_LOG_SUCCESS, requestMethod, requestUrl, time, clientIP, headersJson, handlerName, parameters, responseString);

            // 返回结果
            return result;
        } catch (Throwable e) {
            long time = System.currentTimeMillis() - start;
            log.warn(REQUEST_LOG_FAILURE, requestMethod, requestUrl, time, clientIP, headersJson, handlerName, parameters, e.toString());
            throw e;
        }
    }

    /**
     * 是否排除
     *
     * @param path 路径
     * @return boolean
     */
    private boolean shouldSkip(String path) {
        if (!requestLogProperties.isEnabled()) {
            return true;
        }

        for (String pattern : requestLogProperties.getExcludePath()) {
            if (antPathMatcher.match(pattern, path)) {
                return true;
            }
        }

        return false;
    }

    /**
     * 序列化请求头
     *
     * @param request 请求
     * @return 字符串
     */
    private String serializeHeaders(HttpServletRequest request) {
        List<String> headerNames = WebUtil.getHeaderNames(request);
        if (CollectionUtil.isEmpty(headerNames)) {
            return StringPool.EMPTY_JSON;
        }

        Map<String, String> headersMap = new LinkedHashMap<>();
        for (String name : headerNames) {
            if (isProtectedHeader(name)) {
                headersMap.put(name, requestLogProperties.getProtectedHeaderValue());
            } else {
                headersMap.put(name, request.getHeader(name));
            }
        }

        return serializeObject(headersMap);
    }

    /**
     * 是否是受保护的请求头
     *
     * @param name 请求头名称
     * @return boolean
     */
    private boolean isProtectedHeader(String name) {
        for (String protectedHeader : requestLogProperties.getProtectedHeaders()) {
            if (name.equalsIgnoreCase(protectedHeader)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 序列化方法参数
     *
     * @param parameters 方法参数
     * @return 字符串
     */
    private String serializeParameters(Object[] parameters) {
        if (ArrayUtil.isEmpty(parameters)) {
            return StringPool.EMPTY_ARRAY;
        }

        List<String> list = new ArrayList<>();
        for (Object parameter : parameters) {
            list.add(parameter instanceof String ? (String) parameter : serializeObject(parameter));
        }

        return list.toString();
    }

    /**
     * 安全序列化对象
     */
    private String serializeObject(Object obj) {
        if (obj == null) {
            return StringPool.NULL;
        }

        try {
            return objectMapper.writeValueAsString(obj);
        } catch (JsonProcessingException e) {
            // 避免不可序列化对象导致日志失败
            return obj.getClass().getSimpleName() + StringPool.AT + Integer.toHexString(obj.hashCode());
        }
    }

}
